﻿#region Licence
// <copyright file="Core/AccessControlLayerService.cs" company="Kopigi">
// Copyright © Kopigi 2014
// </copyright>
// ****************************************************************************
// <author>Marc PLESSIS</author>
// <date>11/10/2014</date>
// <project>Nentang.wpf</project>
// <web>http://nentang.codeplex.com</web>
// <license>
// The MIT License (MIT)
// 
// Copyright (c) 2013 Marc Plessis
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// 
// </license>
#endregion
#region using

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using Database.POCO;
using GalaSoft.MvvmLight.Ioc;
using Helpers.Class;
using Interfaces;
using Interfaces.Contracts;
using Interfaces.Enums;

#endregion

namespace Core.Services
{
    /// <summary>
    /// Gestion de la connexion et des droits de l'utilisateur
    /// </summary>
    public class AccessControlLayerService : ImplementNotifyPropertyChanged, IServiceAccessControlLayer, IDisposable
    {
        //Droits réels pour un droit de l'utilisateur
        private readonly Dictionary<string, ValueRightsEnum> _valuesRightDictionary = new Dictionary<string, ValueRightsEnum>();

        //Droits pour les colonnes et tables d'un utilisateur
        private readonly Dictionary<string, ValueRightsEnum> _valuesColumnsRightDictionnaDictionary = new Dictionary<string, ValueRightsEnum>();

        public IServiceDatabase ServiceDatabase
        {
            get { return SimpleIoc.Default.GetInstance<IServiceDatabase>(); }
        }

        private SysUtilisateur _currentUser;
        public SysUtilisateur CurrentUser
        {
            get { return _currentUser; }
            set
            {
                _currentUser = value;
                RaisePropertyChanged(() => CurrentUser);
            }
        }

        /// <summary>
        /// Module accessibles par l'utilisateur
        /// </summary>
        public List<IModuleMetadata> Modules { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public AccessControlLayerService()
        {
            _currentUser = null;
            Modules = new List<IModuleMetadata>();
        }

        /// <summary>
        /// Permet de savoir si le couple utilisateur/mot de passe est correct sur l'application
        /// </summary>
        /// <param name="user">Utilisateur</param>
        /// <param name="password">Mot de passe</param>
        /// <param name="errorsEnum">Erreur possible lors de l'authentification</param>
        /// <returns></returns>
        public bool IsAuth(string user, string password, out AccessControlErrorsEnum errorsEnum)
        {
            //1. On vérifie que l'utilisateur existe bien
            var usersDb = ServiceDatabase.Service.Where<SysUtilisateur>(u => u.Code == user);
            if (usersDb.Any())
            {
                var userDb = usersDb.First();
                if (userDb.IsBloque)
                {
                    errorsEnum = AccessControlErrorsEnum.UtilisateurBloque;
                    return false;
                }
                else
                {
                    //2. Préparation du mot de passe avec le GDS
                    var mdpgds = string.Format("{0}.{1}", password, userDb.Gds);

                    //3. On valide le mot de passe
                    if (HashValue(mdpgds) == userDb.MotDePasse)
                    {
                        errorsEnum = AccessControlErrorsEnum.None;
                        CurrentUser = userDb;

                        //On modifie le GDS avec une nouvelle valeur aléatoire
                        GenerateNewGdsForUser(userDb, password);

                        return true;
                    }
                    else
                    {
                        errorsEnum = AccessControlErrorsEnum.MDPIncorrect;
                        return false;
                    }
                }
            }
            else
            {
                errorsEnum = AccessControlErrorsEnum.UtilisateurInconnu;
                return false;
            }
        }

        /// <summary>
        /// Permet de savoir si l'utilisateur est autorisé à utiliser cette fonction
        /// </summary>
        /// <param name="identifiantDroit"></param>
        /// <param name="retrieveValuesRight"></param>
        /// <returns></returns>
        public bool IsAuthorizedFunction(string identifiantDroit, bool retrieveValuesRight = false)
        {
            if (_currentUser == null)
            {
                return false;
            }

            if (_currentUser.IsAdministrateur)
            {
                return true;
            }
            var droits = ServiceDatabase.Service.Where<SysUtilisateurDroitFonction>(d => d.Droit.IdentifiantDroit.Equals(identifiantDroit));
            if (droits.Any())
            {
                var isAuthorized = false;
                var valuesDroit = 0;

                //On regarde les droits de l'utilisateur
                var droitsUser = droits.Where(d => d.Utilisateur.Code.Equals(_currentUser.Code));
                if (droitsUser.Any())
                {
                    valuesDroit = droitsUser.First().Value;
                    isAuthorized = (valuesDroit > 0) && ((ValueRightsEnum)valuesDroit != ValueRightsEnum.Bloque);
                }

                if (((ValueRightsEnum)valuesDroit != ValueRightsEnum.Bloque) && valuesDroit == 0)
                {
                    //On regarde si l'utilisateur fait parti d'un groupe
                    if (_currentUser.Groupes != null && _currentUser.Groupes.Any())
                    {
                        foreach (var groupe in _currentUser.Groupes)
                        {
                            SysGroupe groupe1 = groupe;
                            var droitsGroup = droits.Where(d => d.Groupe.Code.Equals(groupe1.Code));
                            if (droitsGroup.Any())
                            {
                                valuesDroit = droitsGroup.First().Value;
                                isAuthorized = valuesDroit > 0;
                                if (!isAuthorized)
                                {
                                    //Si l'un des groupes ne posséde pas de droit pour c'est cette valeur qui prend le pas sur les autres
                                    break;
                                }
                            }
                        }
                    }
                }

                if (retrieveValuesRight)
                {
                    //On stocke les droits réels pour l'objet afin de facilement les retrouver par la suite
                    var values = GetDictionnaryValuesForRight(valuesDroit);
                    if (!_valuesRightDictionary.ContainsKey(identifiantDroit))
                    {
                        _valuesRightDictionary.Remove(identifiantDroit);
                    }
                    _valuesRightDictionary.Add(identifiantDroit, values);
                }
                return isAuthorized;
            }
            return false;
        }

        /// <summary>
        /// Permet de savoir si l'utilisateur est autorisé à utiliser cette fonction, récupére en même temps les droits pour l'utilisateur
        /// </summary>
        /// <param name="identifiantDroit"></param>
        /// <param name="rights"></param>
        /// <returns></returns>
        public bool IsAuthorizedFunction(string identifiantDroit, out ValueRightsEnum rights)
        {
            if (_currentUser == null)
            {
                rights = ValueRightsEnum.Aucun;
                return false;
            }
            if (_currentUser.IsAdministrateur)
            {
                rights = ValueRightsEnum.Tous;
                return true;
            }

            var droits = ServiceDatabase.Service.Where<SysUtilisateurDroitFonction>(d => d.Droit.IdentifiantDroit.Equals(identifiantDroit)).ToList();
            if (droits.Any())
            {
                var isAuthorized = false;
                var valuesDroit = 0;

                //On regarde les droits de l'utilisateur
                var droitsUser = droits.Where(d => d.Utilisateur.Code.Equals(_currentUser.Code));
                if (droitsUser.Any())
                {
                    valuesDroit = droitsUser.First().Value;
                    isAuthorized = (valuesDroit > 0) && ((ValueRightsEnum)valuesDroit != ValueRightsEnum.Bloque);
                }

                if (((ValueRightsEnum) valuesDroit != ValueRightsEnum.Bloque) && valuesDroit == 0)
                {
                    //On regarde si l'utilisateur fait parti d'un groupe
                    if (_currentUser.Groupes != null && _currentUser.Groupes.Any())
                    {
                        foreach (var groupe in _currentUser.Groupes)
                        {
                            SysGroupe groupe1 = groupe;
                            var droitsGroup = droits.Where(d => d.Groupe != null && d.Groupe.Code.Equals(groupe1.Code));
                            if (droitsGroup.Any())
                            {
                                valuesDroit = droitsGroup.First().Value;
                                isAuthorized = valuesDroit > 0;
                                if (!isAuthorized)
                                {
                                    //Si l'un des groupes ne posséde pas de droit pour, c'est cette valeur qui prend le pas sur les autres
                                    break;
                                }
                            }
                        }
                    }
                }

                //On stocke les droits réels pour l'objet afin de facilement les retrouver par la suite
                rights = GetDictionnaryValuesForRight(valuesDroit);
                return isAuthorized;
            }
            rights = ValueRightsEnum.Aucun;
            return false;
        }

        /// <summary>
        /// Permet de déconnecte run utilisateur et de re-initialiser les informations actuelle sur les droits
        /// </summary>
        public bool Disconnect()
        {
            try
            {
                CurrentUser = null;
                _valuesRightDictionary.Clear();
                _valuesColumnsRightDictionnaDictionary.Clear();
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Permet de sauvegarder un nouveau mot de passe pour l'utilisateur
        /// </summary>
        /// <param name="user"></param>
        /// <param name="password"></param>
        /// <param name="save"></param>
        /// <returns></returns>
        public bool CreateNewPasswordForUser(SysUtilisateur user, string password, bool save = false)
        {
            try
            {
                GenerateNewGdsForUser(user, password, save);
                return true;
            }
            catch (Exception e)
            {
                SimpleIoc.Default.GetInstance<IServiceLogger>().LogFatalError(e);
                return false;
            }
        }

        /// <summary>
        /// Retrouve les droits réels de l'utilisateur pour la fonction
        /// </summary>
        /// <param name="identifiantDroit"></param>
        /// <returns></returns>
        public ValueRightsEnum ValueAuthorizedFunction(string identifiantDroit)
        {
            if (CurrentUser.IsAdministrateur)
            {
                return ValueRightsEnum.Tous;
            }

            if (_valuesRightDictionary.ContainsKey(identifiantDroit))
            {
                ValueRightsEnum value;
                if (_valuesRightDictionary.TryGetValue(identifiantDroit, out value))
                {
                    return value;
                }
            }
            return ValueRightsEnum.Aucun;
        }

        /// <summary>
        /// Retrouve les droits pour une colonne d'une table
        /// </summary>
        /// <param name="table"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        public bool IsAuthorizedToVisualizedColumns(string table, string column)
        {
            if (!_valuesColumnsRightDictionnaDictionary.Any())
            {
                PrepareValuesRightsDictionnary();
            }

            //Vérification du droit
            var isAuthorized = true;
            var droit = string.Format("{0}.{1}", table, column);
            if (_valuesColumnsRightDictionnaDictionary.ContainsKey(droit))
            {
                ValueRightsEnum value;
                if (_valuesColumnsRightDictionnaDictionary.TryGetValue(droit, out value))
                {
                    isAuthorized = value.HasFlag(ValueRightsEnum.Visualisation);
                }
            }
            return isAuthorized;
        }

        /// <summary>
        /// Retrouve les droits asssociés pour une colonne
        /// </summary>
        /// <param name="table"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        public ValueRightsEnum ValueAuthorizedColumns(string table, string column)
        {
            if (_currentUser.IsAdministrateur)
            {
                return ValueRightsEnum.Tous;
            }

            if (!_valuesColumnsRightDictionnaDictionary.Any())
            {
                PrepareValuesRightsDictionnary();
            }

            var droit = string.Format("{0}.{1}", table, column);
            if (_valuesColumnsRightDictionnaDictionary.ContainsKey(droit))
            {
                ValueRightsEnum value;
                if (_valuesColumnsRightDictionnaDictionary.TryGetValue(droit, out value))
                {
                    return value;
                }
            }
            return ValueRightsEnum.Aucun;

        }

        public void Dispose()
        {
            Dispose(true);            
        }

        #region private

        private ValueRightsEnum GetDictionnaryValuesForRight(int value)
        {
            return (ValueRightsEnum)value;
        }

        /// <summary>
        /// Préparation du tableau des droits
        /// </summary>
        private void PrepareValuesRightsDictionnary()
        {
            var listRights = new List<SysUtilisateurDroitColonnes>();
            listRights.AddRange(ServiceDatabase.Service.Where<SysUtilisateurDroitColonnes>(u => u.Utilisateur.Code == CurrentUser.Code));
            foreach (var groupe in CurrentUser.Groupes)
            {
                SysGroupe groupe1 = groupe;
                listRights.AddRange(ServiceDatabase.Service.Where<SysUtilisateurDroitColonnes>(u => u.Groupe.Code == groupe1.Code));
            }


            foreach (var right in listRights)
            {
                var droitTableColonne = string.Format("{0}.{1}", right.Table, right.Colonne);
                var values = GetDictionnaryValuesForRight(right.Value);
                _valuesColumnsRightDictionnaDictionary.Add(droitTableColonne, values);
            }
        }

        /// <summary>
        /// Permet de générer un nouveau GDS et sauvegarder le mot de passe associé à ce nouveau GDS
        /// afin de diminuer les chances de prédiction du mot de passe
        /// </summary>
        /// <param name="user"></param>
        /// <param name="password"></param>
        /// <param name="save"></param>
        private void GenerateNewGdsForUser(SysUtilisateur user, string password, bool save = true)
        {
            //1. Génération d'un nouveau GDS
            var rngProvider = new RNGCryptoServiceProvider();
            var randomNumber = new byte[4];
            rngProvider.GetBytes(randomNumber);
            var newGds = Math.Abs(BitConverter.ToInt32(randomNumber, 0));

            //2. Cryptage du nouveau GDS avec le mot de passe
            var newMdpgds = string.Format("{0}.{1}", password, newGds);
            var newMdp = HashValue(newMdpgds);

            //3. Enregistrement de ces nouvelles valeurs
            user.MotDePasse = newMdp;
            user.Gds = newGds.ToString(CultureInfo.InvariantCulture);
            if (save)
            {
                ServiceDatabase.Service.Save<SysUtilisateur>(user);    
            }
        }

        #endregion

        #region protected

        protected virtual void Dispose(bool disposing)
        {
            _valuesRightDictionary.Clear();
            _currentUser = null;
        }

        /// <summary>
        /// Hash la valeur demandé suivant l'algo SHA256
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected string HashValue(string value)
        {
            var mHash = new SHA256CryptoServiceProvider();
            var byteValue = Encoding.UTF8.GetBytes(value);
            var byteHash = mHash.ComputeHash(byteValue);
            mHash.Clear();
            return Convert.ToBase64String(byteHash);
        }

        #endregion
    }
}
